package core;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.StringReader;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.xml.sax.SAXException;

import codeGeneration.ActionForm;
import codeGeneration.BeanGenerator;
import codeGeneration.ConfigGenerator;
import codeGeneration.ControllerLayerGenerator;
import codeGeneration.Event;
import codeGeneration.ValidatorGenerator;
import codeGeneration.ViewLayerGenerator;
import codeGeneration.bean.ClassGenerator;
import codeGeneration.bean.HibernateClassGenerator;
import codeGeneration.bean.JDBCClassGenerator;
import codeGeneration.persistent.PersistentGenerator;
import codeGeneration.persistent.HibernatePersistentGenerator;
import codeGeneration.persistent.JDBCPersistentGenerator;
import codeGeneration.service.ServiceGenerator;
import databaseTransaction.Command;
import databaseTransaction.DataSourceGenerator;
import databaseTransaction.ModelLayer;
import databaseTransaction.Table;
import databaseTransaction.TableField;

import parser.Parser;
import util.ClassInformation;
import util.Configuration;
import util.ConnectionConfig;
import util.FolderPath;
import util.InformationForGenerate;
import util.POMPlace;
import util.Relationship;
import util.Tag;
import util.UserTerminatingException;
import lexer.Lexer;
import lexer.TokenConstant;

public class Main {
	private String actionFormName;
	private String beanName;
	private Configuration config;
	private String projectPath;
	private String excelTemplateFilePath;
	private IProgressMonitor monitor;
	private FolderPath folderPath;
	private ConnectionConfig connConfig;
	private ActionForm actionForm;
	private POMPlace pomPlace;
	private InformationForGenerate informationForGenerate;

	public Main(String path, String packageName, IProgressMonitor monitor,
			final Shell shell) throws ParserConfigurationException,
			SAXException, IOException {
		informationForGenerate = new InformationForGenerate();

		this.projectPath = path.substring(0, path.lastIndexOf('/'));
		this.excelTemplateFilePath = path;
		this.monitor = monitor;

		File javaPath = new File(projectPath + "/src/main/java/"
				+ packageName.replace('.', '/'));
		if (!javaPath.isDirectory() && !javaPath.exists()) {
			throw new FileNotFoundException(
					"Init source code path failed!\nPlease consider input package name.");
		}

		this.pomPlace = new POMPlace();
		loadDefaultFolderPath(packageName);

		config = new Configuration();
		try {
			config.loadRealConfigFile(folderPath.getWebinfPath()
					+ "/spring/appServlet/servlet-context.xml");
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
			throw new ParserConfigurationException(
					"Error about parser configuration "
							+ "in parse XML config file");
		} catch (SAXException e) {
			Display.getDefault().syncExec(new Runnable() {
				@Override
				public void run() {
					MessageDialog.openError(shell, "Error",
							"Parser can't parse XML config file, "
									+ "the system will delete it and generate "
									+ "a new config file");
				}
			});
		} catch (IOException e) {
			throw new IOException("Can't read XML config file");
		}
	}

	private void generateCommon(int numberOfDone) throws Exception {
		monitor.setTaskName("Read excel file");
		ExcelManipulation excelManipulation = new ExcelManipulation(
				excelTemplateFilePath);

		try {
			excelManipulation.processingExcelFile();
		} catch (Exception e) {
			e.printStackTrace();
			throw new Exception("Can't read Excel template file");
		}

		System.out.println(excelManipulation.getCode());
		updateMonitorTask(numberOfDone,
				"Read action form excel file successfully");
		numberOfDone += 10;

		Parser parser = new Parser(new Lexer(new StringReader(
				excelManipulation.getCode())));
		try {
			parser.parse();
		} catch (Exception e) {
			e.printStackTrace();
			throw new Exception("Parser can't parse ");
		}

		updateMonitorTask(numberOfDone, "Parse action form file successfully");
		numberOfDone += 10;

		List<Event> events = parser.getEvents();
		actionForm = parser.getActionForm();
		actionFormName = actionForm.getName();
		beanName = actionForm.getBeanName();

		// Validator generator

		try {
			generateValidator(folderPath, events);
		} catch (IOException e) {
			throw e;
		}

		updateMonitorTask(numberOfDone, "Generate validator successfully");
		numberOfDone += 10;

		// -----------------------------------------------------

		// Controller generator

		try {
			generateController(folderPath, actionFormName, beanName);
		} catch (IOException e) {
			throw e;
		}
		updateMonitorTask(numberOfDone, "Generate controller successfully");
		numberOfDone += 10;

		// -----------------------------------------------------

		// View generator
		if (informationForGenerate.getViewName() != null)
			try {
				generateView(actionForm, events, folderPath.getViewPath());
			} catch (IOException e) {
				throw e;
			}

		updateMonitorTask(numberOfDone, "Generate action form successfully");
		numberOfDone += 10;
	}

	public void generate() throws Exception {
		generateCommon(0);

		// Bean generator

		generateBean();

		// -----------------------------------------------------

		// Config generator

		generateExtra(folderPath);

		// -----------------------------------------------------

		// -----------------------------------------------------
	}

	public void generate(String modelTemplateFilePlace, boolean isJDBC,
			boolean isGenerateCommon) throws IOException,
			ParserConfigurationException, SAXException, TransformerException,
			Exception {
		int numberOfDone = 10;

		pomPlace.addDependency("mysql");
		ExcelManipulation excelManipulation = new ExcelManipulation(
				modelTemplateFilePlace);
		try {
			excelManipulation.processingExcelFile();
		} catch (Exception e) {
			e.printStackTrace();
			throw new Exception("Can't read model excel template file");
		}
		System.out.println(excelManipulation.getCode());

		updateMonitorTask(numberOfDone,
				"Read model layer excel file successfully");
		numberOfDone += 5;

		Parser parser = new Parser(new Lexer(new StringReader(
				excelManipulation.getCode())));
		try {
			parser.parse();
		} catch (Exception e) {
			e.printStackTrace();
			throw new Exception("Parser can't parse excel template file");
		}

		updateMonitorTask(numberOfDone, "Parser parse model layer successfully");
		numberOfDone += 5;

		List<Table> tables = parser.getTables();
		if (!integrityBetweenTables(tables))
			throw new IOException("Wrong type in database?");

		try {
			generateModelLayer(tables, isJDBC);
		} catch (IOException e) {
			throw e;
		}

		updateMonitorTask(numberOfDone, "Generate model layer successfully");
		numberOfDone += 10;

		if (isGenerateCommon)
			generateCommon(numberOfDone);

		try {
			generateExtra(folderPath);

		} catch (IOException e) {
			throw e;
		} catch (ParserConfigurationException e) {
			throw e;
		} catch (SAXException e) {
			throw e;
		} catch (TransformerException e) {
			throw e;
		}
	}

	private void generateModelLayer(List<Table> tables, boolean isJDBC)
			throws IOException, SQLException, UserTerminatingException {
		if (isJDBC)
			pomPlace.addDependency("spring-jdbc");
		else {
			pomPlace.addDependency("hibernate-validator");
			pomPlace.addDependency("dom4j");
			pomPlace.addDependency("hibernate-core");
			pomPlace.addDependency("jboss-logging");
			pomPlace.addDependency("spring-orm");
			pomPlace.addDependency("javassist");
		}

		ModelLayer modelLayer = new ModelLayer(connConfig);
		modelLayer.setTables(tables);

		try {
			modelLayer.createTable();
		} catch (SQLException e) {
			e.printStackTrace();
			throw e;
		} catch (UserTerminatingException e) {
			e.printStackTrace();
			throw e;
		}

		DataSourceGenerator dsGenerator = new DataSourceGenerator();

		try {
			dsGenerator.generateProperties(folderPath, connConfig);
		} catch (IOException e) {
			e.printStackTrace();
			throw new IOException("Error in generate jdbc.properties");
		}

		dsGenerator.getDataSourceBean(connConfig.getTypeOfDBS(), config);
		config.setPlaceHolder("jdbc.properties");

		// Check and change command (get, set) between tables

		for (Table table : tables) {
			table.setGenerateHibernate(checkGeneratePersistentOrNot(table));

			if (!table.isGenerateHibernate()) {
				List<Command> commands = table.getCommands();

				List<TableField> allOfField = new ArrayList<TableField>(
						table.getId());
				allOfField.addAll(table.getTableFields());

				for (Command command : commands)
					if (command.getTypeOfCommand() == TokenConstant.SET) {
						List<TableField> whereFieldNames = command
								.getListOfWhereFieldName();
						if (whereFieldNames.size() == 1) {
							TableField whereFieldName = whereFieldNames.get(0);
							for (TableField tf : allOfField)
								if (tf.getExtra().contains(TokenConstant.FK)
										&& tf.getName().equals(
												whereFieldName.getName())) {
									String tempTableName = tf.getRelationship()
											.getTableName();

									for (Table tempTable : tables)
										if (tempTable.getName().equals(
												tempTableName)) {
											tempTable.addCommand(command);
										}
								}
						}
					}
			}

		}

		// Persistent generator
		String beanPackageName = folderPath.getPackageName() + ".bean";
		informationForGenerate.setPackageOfBean(beanPackageName);

		String persistentPackageName = folderPath.getPackageName()
				+ ".persistent" + (isJDBC ? ".jdbc" : ".hibernate");

		for (Table table : tables) {
			if (table.isGenerateHibernate()) {
				ClassGenerator classGenerator;
				if (isJDBC)
					classGenerator = new JDBCClassGenerator(table,
							folderPath.getSrcPath(), beanPackageName);
				else
					classGenerator = new HibernateClassGenerator(table,
							folderPath.getSrcPath(), beanPackageName,
							folderPath.getPackageName());

				classGenerator.generateClass();

				PersistentGenerator persistentGenerator;
				if (isJDBC)
					persistentGenerator = new JDBCPersistentGenerator(table,
							folderPath.getSrcPath(), persistentPackageName,
							folderPath.getPackageName(), informationForGenerate);
				else
					persistentGenerator = new HibernatePersistentGenerator(
							table, folderPath.getSrcPath(),
							persistentPackageName, folderPath.getPackageName(),
							config, informationForGenerate);

				persistentGenerator.setClassName(table.getName());
				persistentGenerator.generatePersistent();

				generateService(table.getName());
			}
		}
	}

	private boolean integrityBetweenTables(List<Table> tables) {
		for (Table table : tables) {
			List<TableField> allOfField = new ArrayList<TableField>(
					table.getId());
			allOfField.addAll(table.getTableFields());

			for (TableField tf : allOfField) {
				if (tf.getExtra() != null && tf.getExtra().size() > 0
						&& tf.getExtra().contains(TokenConstant.FK)) {
					if (tf.getExtra().size() < 3)
						return false;

					List<Object> extras = tf.getExtra();
					int indexOfTableName = -1, indexOfTableFieldName = -1;

					if (extras.contains(TokenConstant.PK) && extras.size() < 4)
						return false;

					indexOfTableName = 1;
					indexOfTableFieldName = 2;

					String tableName = extras.get(indexOfTableName).toString();
					String tableFieldName = extras.get(indexOfTableFieldName)
							.toString();
					if (!checkExistTable(tables, table, tableName,
							tableFieldName))
						return false;
				}

				if (tf.getRelationship() != null) {
					Relationship relationship = tf.getRelationship();

					if (relationship.getRelationship() == TokenConstant.MANYTOMANY
							|| relationship.getRelationship() == TokenConstant.ONETOMANY)
						if (!checkExistTable(tables, table,
								relationship.getTableName(), null))
							return false;
				}
			}
		}

		return true;
	}

	private boolean checkExistTable(List<Table> tables, Table currentTable,
			String tableName, String tableFieldName) {
		boolean flag = false;

		for (Table tempTable : tables) {
			if ((tempTable != currentTable)
					&& (tableName.equals(tempTable.getName()))) {
				if (tableFieldName == null)
					return true;

				for (TableField id : tempTable.getId())
					if (id.getName().equals(tableFieldName)) {
						flag = true;
						break;
					}
			}
		}
		return flag;
	}

	private boolean checkGeneratePersistentOrNot(Table table) {
		for (TableField tableField : table.getTableFields()) {
			Relationship relationship = tableField.getRelationship();
			if (relationship == null)
				return true;

			if (!tableField.getExtra().contains("FK")
					|| relationship.getRelationship() != TokenConstant.MANYTOMANY)
				return true;

		}

		return false;
	}

	private void updateMonitorTask(int worked, String status) {
		monitor.setTaskName(status);
		monitor.worked(worked);
	}

	private void loadDefaultFolderPath(String packageName) {
		folderPath = new FolderPath();

		folderPath.setBasePath(projectPath + "/src/main");
		folderPath.setSrcPath(folderPath.getBasePath() + "/java");
		folderPath.setWebinfPath(folderPath.getBasePath() + "/webapp/WEB-INF");
		folderPath.setPackageName(packageName);
		folderPath.setMessagesPath(folderPath.getWebinfPath() + "/messages");
		folderPath.setViewPath(folderPath.getWebinfPath() + "/views");
	}

	public void addClassInformationToList(String id, String className,
			Properties prop) {
		ClassInformation classInformation = config.checkWithIDOfBean(id);
		classInformation.setClassName(className);
		classInformation.setChildTags(Tag.makeTagForBeanProperty(prop));
	}

	private void generateValidator(FolderPath folderPath, List<Event> events)
			throws IOException {
		String packageName = folderPath.getPackageName() + ".validator";

		ValidatorGenerator validatorGenerator = new ValidatorGenerator(
				folderPath.getSrcPath(), beanName, packageName,
				folderPath.getMessagesPath(), events);

		try {
			validatorGenerator.generateValidator();
		} catch (IOException e) {
			throw e;
		}
		String validatorClassName = validatorGenerator.getDefaultClassName();
		informationForGenerate.setNameOfValidatorClass(validatorClassName);
	}

	private void generateController(FolderPath folderPath,
			String actionFormName, String beanName) throws IOException {
		String viewName = null;
		ControllerLayerGenerator controllerGenerator = new ControllerLayerGenerator(
				folderPath, actionFormName,
				informationForGenerate.getNameOfValidatorClass(), beanName);
		viewName = controllerGenerator.generate();

		config.setComponentScanPackage(folderPath.getPackageName());
		config.setAnnotation(true);

		informationForGenerate.setViewName(viewName);
	}

	private void generateView(ActionForm actionForm, List<Event> events,
			String viewPath) throws IOException {
		ViewLayerGenerator viewLayerGenerator = new ViewLayerGenerator(
				viewPath, informationForGenerate.getViewName());

		viewLayerGenerator.setActionForm(actionForm);
		viewLayerGenerator.setEvents(events);
		viewLayerGenerator.generateControls();
	}

	private void generateExtra(FolderPath folderPath) throws IOException,
			ParserConfigurationException, SAXException, TransformerException {
		ConfigGenerator configGenerator = new ConfigGenerator(config,
				folderPath);
		try {
			configGenerator.generate("root-context.xml");
			configGenerator.generate("servlet-context.xml");
			configGenerator.generate("web.xml");
		} catch (IOException e) {
			e.printStackTrace();
			throw new IOException("Error in generate config file");
		}

		try {
			pomPlace.addDependencyToPOM(projectPath);
		} catch (IOException e) {
			e.printStackTrace();
			throw new IOException("Error when read XML POM file");
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
			throw new ParserConfigurationException(
					"Error when config parser POM XML file");
		} catch (SAXException e) {
			e.printStackTrace();
			throw new ParserConfigurationException(
					"Error when parser parse POM XML file");
		} catch (TransformerException e) {
			e.printStackTrace();
			throw new TransformerException("Error when parser transform "
					+ "from input xml file to output xml file");
		}

		updateMonitorTask(80, "Generate config file successfully");
	}

	private void generateBean() {
		BeanGenerator beanGenerator = new BeanGenerator(
				folderPath.getSrcPath(), folderPath.getPackageName() + ".bean",
				actionForm);

		beanGenerator.generateBean();
		updateMonitorTask(70, "Generate bean successfully");
	}

	private void generateService(String beanName) throws IOException {
		String servicePackageName = folderPath.getPackageName() + ".service";
		ServiceGenerator serviceGenerator = new ServiceGenerator(
				informationForGenerate, beanName, servicePackageName,
				folderPath.getSrcPath());
		serviceGenerator.generateService();
	}

	public void setConnectionConfig(ConnectionConfig connConfig) {
		this.connConfig = connConfig;
	}
}